#include "http.h"

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;

#define EPOLL_SIZE 60

namespace http_server{
    int HttpServer::ServerInit(const string& ip,short port){
        int listen_sock = socket(AF_INET,SOCK_STREAM,0);
        if(listen_sock < 0){
            perror("listen_sock");
            return -1;
        }

        //要给socket加上一个选项，能够重用我们的连接
        //避免出现大量的TIME_WAIT
        int opt = 1;
        setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

        //初始化socket
        sockaddr_in server_addr;
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = inet_addr(ip.c_str());
        server_addr.sin_port = htons(port);
 
        //绑定端口号
        int ret = bind(listen_sock,(sockaddr*)&server_addr,sizeof(server_addr));
        if(ret < 0){
            perror("bind");
            return -1;
        }
        
        //将socket变为listen的
        ret = listen(listen_sock,5);
        if(ret < 0){
            perror("listen");
            return -1;
        }

        LOG(INFO) << "ServerInit OK!\n";

        daemon(1,0);//将该进程变为守护进程放到后台
        int epoll_fd = epoll_create(500);  //创建epoll对象
        if(epoll_fd < 0){
            perror("epoll_create");
            return -1;
        }
        epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.fd = listen_sock;

        //首先把监听sock添加进去
        ret = epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ev);
        if(ret < 0){
            perror("epoll_ctl");
            return -1;
        }
        while(1){
            epoll_event events[EPOLL_SIZE]; //创建epoll_event结构体数组
            int size = epoll_wait(epoll_fd,events,sizeof(events) / sizeof(events[0]),-1);
            if(size < 0){
                perror("epoll_wait");
                continue;
            }
            if(size == 0){
                LOG(INFO) << "epoll_wait timeout!" << "\n";
                continue;
            }

            for(int i = 0; i < size; ++i){
                if(!(events[i].events & EPOLLIN))
                    continue;

                if(events[i].data.fd == listen_sock){
                    //处理listen_sock
                    ProcessConnect(listen_sock,epoll_fd);
                }else{
                    //处理new_sock
                    Context* context = new Context();
                    context->new_sock = events[i].data.fd;
                    context->server = this;//通过this调用非静态成员,这个this指向的对象是main函数中创建的对象
                    context->epoll_fd = epoll_fd;
                    
                    //创建新线程，使用新线程完成这次请求的计算
                    pthread_t tid;
                    pthread_create(&tid,NULL,ThreadEntry,context);
                    pthread_detach(tid);
                    //这个地方不能用pthread_join，因为如果第一个请求没执行完，就没法jion
                    //也就没法进入下一个循环进行accept，后面的请求也就没法来了
                }
            }
        }
        close(epoll_fd);
        close(listen_sock);
        return 0;
    }
    
    void HttpServer::ProcessConnect(int listen_sock,int epoll_fd){
        sockaddr_in peer;
        socklen_t len = sizeof(peer);
        //接收请求
        int new_sock = accept(listen_sock,(sockaddr*)&peer,&len);
        if(new_sock < 0){
            perror("accept");
            return;
        }
        epoll_event ev;
        ev.data.fd = new_sock;
        ev.events = EPOLLIN | EPOLLET;
        int ret = epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev);
        if(ret < 0){
            perror("epoll_ctl");
            return;
        }
    }


    void* HttpServer::ThreadEntry(void* arg){
        //准备工作
        Context* context = reinterpret_cast<Context*>(arg);//完成两个没什么关联的指针之间的强转，直接在内存上进行强转
        HttpServer* server = context->server;
        //1.从文件描述符中读取数据（字符串），转化成Request对象
        int ret = 0;
        ret = server->ReadOneRequest(context);
        if(ret < 0){
            LOG(ERROR) << "ReadOneRequest error! " << "\n";
            //用这个函数构造一个404的http响应对象
            server->Process404(context);
            goto END;
        }

        // 通过下面的函数将解析出来的请求打印出来
        // PrintRequest(context->req);

        //2.将Request对象计算生成对应的Response对象
        ret = server->HandlerRequest(context);
        if(ret < 0){
            LOG(ERROR) << "HandlerRequest error! " << "\n";
            server->Process404(context);
            goto END;
        }
    END:
        //处理失败的情况
        //3.对Response对象进行序列化，序列化成一串字符串写回到客户端
        server->WriteOneResponse(context);

        //收尾工作
        close(context->new_sock);
        delete context;
        //一定要在这里释放空间，如果在上面的循环中释放可能上面已经delete了，才执行这个线程函数，那么会导致访问已经释放了的内存
        return NULL;
    }

    //构造一个状态码为404的Response对象
    int HttpServer::Process404(Context* context){
        Response* resp = &context->resp;
        resp->code = 404;
        resp->desc = "Not Found";
        resp->body = "<head><meta http-equiv=\"content-type\""
                     "content=\"text/html;charset=utf-8\">"
                     "</head><h1>404! 您访问的页面不见了QVQ</h1>";
        //这里需要设置字符集来让我们的输出结果不乱码
        //这里在每行末尾用了一个续行符",并在下一行的开始处对应上一个"
        string size = std::to_string(resp->body.size());
        resp->header["Content-Length"] = size;
        return 0;
    }

    
    //从socket中读取字符串，构造生成Request对象
    int HttpServer::ReadOneRequest(Context* context){
        Request* req = &context->req;
        //1.从socket中读取一行数据作为Request的首行
        //  按行读取的分隔符可能有三种，\n, \r, \r\n 
        string first_line;
        FileUtil::ReadLine(context->new_sock,first_line);//首行内容就放到了first_line中
      
        //2.解析首行，获取到请求的method，url
        int ret = ParseFirstLine(first_line,req->method,req->url);
        if(ret < 0){
            LOG(ERROR) << "PraseFirstLine error! first_line=" << first_line << "\n";
            return -1;
        }

        //3.解析url，提取出url_path,query_string
        ret = ParseUrl(req->url,req->url_path,req->query_string);
        LOG(INFO) << req->url << endl;
        if(ret < 0){
            LOG(ERROR) << "PraseUrl error! url=" << req->url << "\n";
            return -1;
        }

        //4.循环地按行读取数据，每次读取到一行数据，就进行一次Header的解析
        //  读到空行表示Header解析完毕
        string header_line;
        while(1){
            FileUtil::ReadLine(context->new_sock,header_line);
            //如果header_line是空行，就退出循环
            //由于ReadLine返回的时候header_line不包含\n等分隔符
            //因此读到空行(即空字符串)的时候，就是表示Header部分完毕了
            if(header_line == ""){
                break;
            }
            ret = ParseHeader(header_line,req->header);
            if(ret < 0){
                LOG(ERROR) << "ParseHeader error! header_line="
                           << header_line << '\n';
            }
        }

        //5.如果是POST请求，但是没有Content-Length字段，认为这次请求失败，返回错误
        Header::iterator it = req->header.find("Content-Length");
        if(req->method == "POST"&& it == req->header.end()){
            LOG(ERROR) << "POST Request has no Content-Length! \n";
            return -1;
        }
        //  如果是GET请求，就不用读body
        if(req ->method == "GET")
            return 0;
        //  如果是POST请求，并且Header中包含了Content-Length字段，继续读取socket，获取到body的内容
        int content_length = 0;
        if(it != req->header.end())
            content_length = atoi(it->second.c_str());
        ret = FileUtil::ReadN(context->new_sock,content_length,req->body);
        if(ret < 0){
            LOG(ERROR) << "ReadN error! content_length=" << content_length << "\n";
            return -1;
        }
        //body中也是urlencode编码
        req->body = StringUtil::UrlDecode(req->body);
        LOG(INFO) << "请求:" << req->body << endl;
        return 0;
    }

    //解析首行，就是安装空格进行分割，分割成是三个部分
    //这是三个部分别就是方法，url，版本号
    int HttpServer::ParseFirstLine(const string& first_line, string& method, string& url){
        std::vector<string> tokens;
        boost::algorithm::split(tokens,first_line,boost::algorithm::is_any_of(" "));
        if(tokens.size() != 3){
            //如果分割出来的数量不是三个部分，即方法，url，版本号，则出错
            LOG(ERROR) << "ParseFirstLine error! split error! first_line="
                << first_line << "\n";
            return -1;
        }

        if(tokens[2].find("HTTP") == string::npos){
            //如果不是HTTP协议，则出错
            LOG(ERROR) << "ParseFirstLine error! version error! first_line="
                << first_line <<"\n";
            return -1;
        }

        method = tokens[0];
        url = tokens[1];
        return 0;
    }

    //解析一个标准的URL其实比较复杂
    //核心思路是以？作为分割
    //？左边来查找url_path，从？右边查找query_string
    //此处只是一个简化版本,只考虑不包含域名，协议以及'#'的情况
    int HttpServer::ParseUrl(string& url,string& url_path,
            string& query_string){
        //把urlendcode进行解码
        url = StringUtil::UrlDecode(url);

        size_t pos = url.find("?");//以？作为分隔符
        if(pos == string::npos){
            //没找到,可能是？后面的那些参数没有，但是有一个url_path
            url_path = url;
            return 0;
        }
        //找到了
        url_path = url.substr(0,pos);//从开始到？号是url路径
        query_string = url.substr(pos+1);//第二个参数如果没写，默认是从当前位置到字符串结尾
        return 0;
    }

    //此函数用于解析一行header
    //此处的实现使用string::find来实现
    //如果使用split的话，可能有如下问题
    //HOST: http://www.baidu.com,这有两个: 
    int HttpServer::ParseHeader(const string& header_line,Header& header){
        size_t pos = header_line.find(':');
        if(pos == string::npos){
            //找不到:说明header的格式有问题
            LOG(ERROR) << "ParseHeader error! has no ':' header_line=" << header_line << "\n";
            return -1;
        }

        if(pos +2 >= header_line.size()){
            //Header在:和后面的value中间还有一个空格，所以是pos+2
            LOG(ERROR) << "ParseHeader error! has no value ! header_line" << header_line << "\n";
            return -1;
        }

        //对unordered_map进行operator[]操作
        header[header_line.substr(0,pos)]=header_line.substr(pos + 2);
        return 0;
    }

    //实现序列化，把Response对象转换成一个string
    //写回到socket中
    //此函数完全按照HTTP协议的要求来构造响应数据
    //实现这个函数的细节可能有很大差异
    //只要遵守HTTP协议的规定就行
    int HttpServer::WriteOneResponse(Context* context){
        //iostream和stringstream的关系类似于
        //pritf和sprintf的关系
        //char buf[1024 * 100] = {0};//优点在于就不用像这样开辟缓冲区了，可以动态开辟
        stringstream ss;
        Response& resp = context->resp;
        //将首行输入到这个ss流中
        ss << "HTTP/1.1 " << resp.code << " " << resp.desc << "\n";
        if(resp.cgi_resp == ""){
            //当前认为是在处理静态页面
            //将Header输入到这个ss流中
            ss << "Content-Type: text/html; charset=utf-8" << "\n";
            for(auto item : resp.header){
                ss << item.first << ":" << item.second << "\n";
            }
            //将body输入到这个ss流中
            ss << "\n";//body和Header之间的空行
            ss << resp.body;
        }else{
            //当前认为是在处理CGI生成的页面
            //cgi_resp同事包含了相应数据的header、空行和body
            ss << resp.cgi_resp;
        }

        //将ss流中的内容写入到socket中
        //stringstream对象中包含了一个string对象
        //string对象中又包含了一个char*C风格字符串
        //取到这两个对象的过程都没有发生拷贝
        //真正的存储数据的缓冲区是这个char*
        const string& str = ss.str();//获取到ss流中的那个string对象
        write(context->new_sock,str.c_str(),str.size());
        epoll_ctl(context->epoll_fd,EPOLL_CTL_DEL,context->new_sock,NULL);
        return 0;
    }

    //通过输入的Request对象计算生成Response对象
    //1.静态文件
    //  a).GET请求，并且没有query_string作为参数
    //2.动态生成页面(使用CGI的方式来动态生成)
    //  a).GET请求，但是有query_string作为参数
    //  b).POST请求
    int HttpServer::HandlerRequest(Context* context){
        Request& req = context->req;
        Response& resp = context->resp;
        resp.code = 200;
        resp.desc = "OK";

        //判定当前的处理方式是按照静态文件处理还是动态页面生成
        if(req.method == "GET"&&req.query_string == ""){
            if(req.url_path == "/"){
                string tmp1 ="/cgi_index";
                req.url_path = tmp1;
                req.query_string = "user=";
                LOG(INFO) << req.url_path << " " << req.query_string << endl;
                return context->server->ProcessCGI(context);
            }
            return context->server->ProcessStaticFile(context);
        } else if((req.method == "GET"&&req.query_string != "")
            || req.method == "POST"){
            return context->server->ProcessCGI(context);
        }else{
            LOG(ERROR) << "Unsupport Method! method=" << req.method << "\n";
            return -1;
        }
        return 0;
    }

    //处理静态文件
    //1.通过Request中的url_path字段计算出文件在磁盘上的路径是什么
    //  例如url_path  /index.html
    //  想要得到的磁盘上的文件就是 ./MyRootDir/index.html，.就是当前的可执行文件的目录，
    //  例如url_path  /image/1.jpg
    //  想要得到的磁盘上的文件就是 ./MyRootDir/image/1.jpg
    //  这个MyRootDir是自己约定的根目录
    //2.打开文件，将文件中的所有内容读取出来放到Response结构体的body中
    int HttpServer::ProcessStaticFile(Context* context){
        const Request& req = context->req;
        Response& resp = context->resp;

        //1.获取到静态文件的完整路径
        string file_path;
        GetFilePath(req.url_path,file_path);
        //2.打开并读取完整的文件
        int ret = FileUtil::ReadAll(file_path,resp.body);
        if(ret < 0){
            LOG(ERROR) << "ReadAll error! file_path=" << file_path << "\n";
            return -1;
        }
        return 0;
    }

    //通过url_path，找到对应的文件路径
    //例如，请求url 可能是http://192.168.224.135:9090/
    //这种情况下 url_path可能是/
    //此时等价于请求/index.html
    //例如，请求url 可能是http://192.168.224.135:9090/image/
    //这种情况下url_path是/image
    //如果url_path指向的是一个目录，就尝试在这个目录下访问
    //index.html文件(这个策略也是一种简单约定)
    void HttpServer::GetFilePath(const string& url_path,string& file_path){
        file_path = "./MyRootDir" + url_path;
        //判定一个路径是普通文件还是目录文件
        //1. Linux的stat函数
        //2. 通过boost库重点filesystem模块来进行判定
        if(FileUtil::IsDir(file_path)){
            //1. /image/ 
            //2. /image 
            //这两种可能都是一个目录，第二种需要在末尾加上一个'/'
            //这样给一个目录则默认访问该目录下的index.html文件          
            if(file_path.back() != '/'){
                file_path.push_back('/');
            }
            file_path += "index.tpl";
        }
        return;
    }

    //
    int HttpServer::ProcessCGI(Context* context){
        //1.创建一对匿名管道（管道是半双工的，父子进程要双向通信）
        
        Request& req = context->req;
        string file_path = "./MyRootDir";
        file_path += req.url_path;
        LOG(INFO) << req.url_path << "  "<< file_path.c_str() << endl;
        
        Response& resp = context->resp;
        int fd1[2], fd2[2];
        pipe(fd1);
        pipe(fd2);
        int father_write = fd1[1];
        int child_read = fd1[0];
        int child_write = fd2[1];
        int father_read = fd2[0];
        //2.设置环境变量
        //如果想要仅仅使用管道完成进程间通信的话，需要额外的约定协议
        //此处的协议是指父进程按照什么样的格式组织数据并写入管道
        //子进程就需要按照什么样的格式从管道中读取数据
        //  a)METHOD请求方法
        string env1;
        string env2;
        if(req.method == "GET"){
            //  b)GET方法 QUERY_STRING 请求的参数
            env1 = "REQUEST_METHOD=GET";
            env2 = "QUERY_STRING=" + req.query_string;
        }else if(req.method == "POST"){
            //  c)POST方法，就设置CONTENT_LENGTH
            env1 = "REQUEST_METHOD=POST";
            auto pos = req.header.find("Content-Length");
            env2 = "CONTENT_LENGTH=" + pos->second;
        }

        string cookie = req.header["Cookie"];
        string env3 = "HTTP_COOKIE=";
        if(!cookie.empty()) 
           env3 += cookie; 

        char* const env[] = {const_cast<char*>(env1.c_str()),
                            const_cast<char*>(env2.c_str()),
                            const_cast<char*>(env3.c_str()),NULL};

        pid_t ret = fork();
        if(ret < 0){
            perror("fork");
            goto END;
        }else if(ret > 0){
            //3.fork，父进程流程
            close(child_read);
            close(child_write);
            //  a)如果是POST请求，父进程就要把body写入到管道中
            if(req.method == "POST"){
                write(father_write,req.body.c_str(),req.body.size());
            }
            //  b)阻塞式地读取管道，尝试把子进程的结果读取出来，并且放到Response对象中
            FileUtil::ReadAll(father_read,resp.cgi_resp);
            //  c)对子进程进行进程等待（为了避免僵尸进程）
            wait(NULL);//不关注子进程的退出结果
        }else{
            //4.fork，子进程流程
            close(father_read);
            close(father_write);
            cout << req.url_path << endl;
            //  a)把标准输入和标准输出进行重定向到匿名管道
            dup2(child_read,0);//从标准输入读，等价于从child_read读
            dup2(child_write,1);//往标准输出写，等价于往child_father写
            //  b)先获取到要替换的可执行文件是哪个(通过url_path获取)
            
            //  c)进行程序的程序替换
            execle(file_path.c_str(),file_path.c_str(),NULL,env);
            //  d)由我们的CGI可执行程序完成动态页面的计算，并且写回数据到管道中
            //    这部分逻辑需要放到另外的单独的文件中实现
            //    并且根据该文件编译生成CGI可执行程序
        }
    END://统一处理收尾工作
        //上面关过了，但是多关一次问题不大
        close(father_read);
        close(father_write);
        close(child_read);
        close(child_write);
        return 0;
    }

    /////////////////////////////////
    //以下是测试函数
    ////////////////////////////////
    void HttpServer::PrintRequest(const Request& req){
        LOG(DEBUG) << "Request:"<<"\n"
                   << req.method << " " << req.url <<"\n"
                   << req.url_path << " " << req.query_string << "\n";
        
        //C++11中的范围for循环
        for(auto it : req.header){
            LOG(DEBUG) << it.first << ":" << it.second << "\n";
        }

        LOG(DEBUG) << "\n";
        LOG(DEBUG) << req.body << "\n";
    }

}//end http_server
